from .relation import Connected
from .yinyang import YinYang
from .wuxing import WuXing
from .tiangan import Gan
from .consts.zhi_consts import CANGGANS
from .zp_utils import combine


class Zhi:
    """
    地支：只使用get_item返回指定的对象
    """
    _cache = {}
    _inited = False

    def __new__(cls, name, no, yinyang, wuxing, canggans):
        if cls._inited:
            raise RuntimeError(f'{cls.__name__} 已经加载，不能直接实例化，请使用 {cls.__name__}.get_item')
        if name in cls._cache:
            return cls._cache[name]
        else:
            self = super().__new__(cls)
            cls._cache[name] = self
            return self

    def __init__(self, name, no, yinyang, wuxing, canggans):
        self.name = name
        self.no = no  # 编号
        self.yinyang = YinYang.get_item(yinyang)
        self.wuxing = WuXing.get_item(wuxing)
        self.gans = []
        for cg in canggans:
            self.gans.append(Gan.get_item(cg))

    @classmethod
    def init_zhis(cls, items):
        """
        初始化的时候调用一次
        """
        for name, yinyang, wuxing, no in items:
            canggans = CANGGANS[name]
            cls(name, no, yinyang, wuxing, canggans)
        cls._inited = True

    @classmethod
    def get_item(cls, name):
        """
        返回指定的对象
        """
        return cls._cache[name]

    def __str__(self):
        cgs = ','.join([cg.name for cg in self.gans])
        return f'{self.name}{self.yinyang.name}{self.wuxing.name}[{cgs}]'


class ZhiXing:
    """
    地支相刑
    """
    _cache = {}
    _inited = False

    def __new__(cls, zhis, name):
        if cls._inited:
            raise RuntimeError(f'{cls.__name__} 已经加载，不能直接实例化，请使用 {cls.__name__}.get_item')
        zhis = sorted(zhis)
        key = tuple(zhis)
        if key in cls._cache:
            return cls._cache[key]
        else:
            self = super().__new__(cls)
            cls._cache[key] = self
            return self

    def __init__(self, zhis, name):
        self.zhis = zhis
        self.name = name

    @classmethod
    def init_zhis_xing(cls, items):
        """
        初始化的时候调用一次
        """
        for *zhis, name in items:
            cls(zhis, name)
        cls._inited = True

    @classmethod
    def get_item(cls, zhis):
        key = tuple(sorted(zhis))
        return cls._cache.get(key)

    @classmethod
    def calc_zhi_xing(cls, mingpan):
        sizhu = mingpan.sizhu
        ans = []
        for num in [3, 2]:
            for zhis in combine([zhu[1] for zhu in sizhu], num):
                match = cls.get_item([dz.zhi.name for dz in zhis])
                if match:
                    ans.append(Connected(match, mingpan, zhis))
        return ans

    def __str__(self):
        zhi_verbose = ''.join(self.zhis)
        return f'{zhi_verbose}{self.name}'


class ZhiChong:
    """
    地支相冲
    """
    _cache = {}
    _inited = False

    def __new__(cls, zhi1, zhi2):
        if cls._inited:
            raise RuntimeError(f'{cls.__name__} 已经加载，不能直接实例化，请使用 {cls.__name__}.get_item')
        if zhi1 > zhi2:
            zhi1, zhi2 = zhi2, zhi1
        key = (zhi1, zhi2)
        if key in cls._cache:
            return cls._cache[key]
        else:
            self = super().__new__(cls)
            cls._cache[key] = self
            return self

    def __init__(self, zhi1, zhi2):
        self.zhi1 = Zhi.get_item(zhi1)
        self.zhi2 = Zhi.get_item(zhi2)

    @classmethod
    def init_zhis_chong(cls, items):
        """
        初始化的时候调用一次
        """
        for zhi1, zhi2 in items:
            cls(zhi1, zhi2)
        cls._inited = True

    @classmethod
    def get_item(cls, zhi1, zhi2):
        if zhi1 > zhi2:
            zhi1, zhi2 = zhi2, zhi1
        key = (zhi1, zhi2)
        return cls._cache.get(key)

    @classmethod
    def calc_zhi_chong(cls, mingpan):
        ans = []
        for dz_cell1, dz_cell2 in combine([zhu[1] for zhu in mingpan.sizhu], 2):
            match = cls.get_item(dz_cell1.zhi.name, dz_cell2.zhi.name)
            if match:
                ans.append(Connected(match, mingpan, [dz_cell1, dz_cell2]))
        return ans

    def __str__(self):
        return f'{self.zhi1.name}{self.zhi2.name}相冲'


class ZhiPo:
    """
    地支相破
    """
    _cache = {}
    _inited = False

    def __new__(cls, zhi1, zhi2):
        if cls._inited:
            raise RuntimeError(f'{cls.__name__} 已经加载，不能直接实例化，请使用 {cls.__name__}.get_item')
        if zhi1 > zhi2:
            zhi1, zhi2 = zhi2, zhi1
        key = (zhi1, zhi2)
        if key in cls._cache:
            return cls._cache[key]
        else:
            self = super().__new__(cls)
            cls._cache[key] = self
            return self

    def __init__(self, zhi1, zhi2):
        self.zhi1 = Zhi.get_item(zhi1)
        self.zhi2 = Zhi.get_item(zhi2)

    @classmethod
    def init_zhis_po(cls, items):
        """
        初始化的时候调用一次
        """
        for zhi1, zhi2 in items:
            cls(zhi1, zhi2)
        cls._inited = True

    @classmethod
    def get_item(cls, zhi1, zhi2):
        if zhi1 > zhi2:
            zhi1, zhi2 = zhi2, zhi1
        key = (zhi1, zhi2)
        return cls._cache.get(key)

    @classmethod
    def calc_zhi_po(cls, mingpan):
        sizhu = mingpan.sizhu
        ans = []
        for dz_cell1, dz_cell2 in combine([zhu[1] for zhu in sizhu], 2):
            match = cls.get_item(dz_cell1.zhi.name, dz_cell2.zhi.name)
            if match:
                ans.append(Connected(match, mingpan, [dz_cell1, dz_cell2]))
        return ans

    def __str__(self):
        return f'{self.zhi1.name}{self.zhi2.name}相破'


class ZhiHai:
    """
    地支相害
    """
    _cache = {}
    _inited = False

    def __new__(cls, zhi1, zhi2):
        if cls._inited:
            raise RuntimeError(f'{cls.__name__} 已经加载，不能直接实例化，请使用 {cls.__name__}.get_item')
        if zhi1 > zhi2:
            zhi1, zhi2 = zhi2, zhi1
        key = (zhi1, zhi2)
        if key in cls._cache:
            return cls._cache[key]
        else:
            self = super().__new__(cls)
            cls._cache[key] = self
            return self

    def __init__(self, zhi1, zhi2):
        self.zhi1 = Zhi.get_item(zhi1)
        self.zhi2 = Zhi.get_item(zhi2)

    @classmethod
    def init_zhis_hai(cls, items):
        """
        初始化的时候调用一次
        """
        for zhi1, zhi2 in items:
            cls(zhi1, zhi2)
        cls._inited = True

    @classmethod
    def get_item(cls, zhi1, zhi2):
        if zhi1 > zhi2:
            zhi1, zhi2 = zhi2, zhi1
        key = (zhi1, zhi2)
        return cls._cache.get(key)

    @classmethod
    def calc_zhi_hai(cls, mingpan):
        sizhu = mingpan.sizhu
        ans = []
        for dz_cell1, dz_cell2 in combine([zhu[1] for zhu in sizhu], 2):
            match = cls.get_item(dz_cell1.zhi.name, dz_cell2.zhi.name)
            if match:
                ans.append(Connected(match, mingpan, [dz_cell1, dz_cell2]))
        return ans

    def __str__(self):
        return f'{self.zhi1.name}{self.zhi2.name}相害'


class ZhiAnHe:
    """
    地支暗合
    """
    _cache = {}
    _inited = False

    def __new__(cls, zhi1, zhi2):
        if cls._inited:
            raise RuntimeError(f'{cls.__name__} 已经加载，不能直接实例化，请使用 {cls.__name__}.get_item')
        if zhi1 > zhi2:
            zhi1, zhi2 = zhi2, zhi1
        key = (zhi1, zhi2)
        if key in cls._cache:
            return cls._cache[key]
        else:
            self = super().__new__(cls)
            cls._cache[key] = self
            return self

    def __init__(self, zhi1, zhi2):
        self.zhi1 = Zhi.get_item(zhi1)
        self.zhi2 = Zhi.get_item(zhi2)

    @classmethod
    def init_zhis_an_he(cls, items):
        """
        初始化的时候调用一次
        """
        for zhi1, zhi2 in items:
            cls(zhi1, zhi2)
        cls._inited = True

    @classmethod
    def get_item(cls, zhi1, zhi2):
        if zhi1 > zhi2:
            zhi1, zhi2 = zhi2, zhi1
        key = (zhi1, zhi2)
        return cls._cache.get(key)

    @classmethod
    def calc_zhi_an_he(cls, mingpan):
        sizhu = mingpan.sizhu
        ans = []
        for dz_cell1, dz_cell2 in combine([zhu[1] for zhu in sizhu], 2):
            match = cls.get_item(dz_cell1.zhi.name, dz_cell2.zhi.name)
            if match:
                ans.append(Connected(match, mingpan, [dz_cell1, dz_cell2]))
        return ans

    def __str__(self):
        return f'{self.zhi1.name}{self.zhi2.name}暗合'


class ZhiHe:
    """
    地支相合
    """
    _cache = {}
    _inited = False

    def __new__(cls, zhi1, zhi2, wuxing, m_dz):
        if cls._inited:
            raise RuntimeError(f'{cls.__name__} 已经加载，不能直接实例化，请使用 {cls.__name__}.get_item')
        if zhi1 > zhi2:
            zhi1, zhi2 = zhi2, zhi1
        key = (zhi1, zhi2)
        if key in cls._cache:
            return cls._cache[key]
        else:
            self = super().__new__(cls)
            cls._cache[key] = self
            return self

    def __init__(self, zhi1, zhi2, wuxing, m_dz):
        self.zhi1 = Zhi.get_item(zhi1)
        self.zhi2 = Zhi.get_item(zhi2)
        self.wuxing = WuXing.get_item(wuxing)
        self.m_dz = m_dz

    @classmethod
    def init_zhis_he(cls, items):
        """
        初始化的时候调用一次
        """
        for zhi1, zhi2, wuxing, m_dz in items:
            cls(zhi1, zhi2, wuxing, m_dz.replace(' ', ''))
        cls._inited = True

    @classmethod
    def get_item(cls, zhi1, zhi2):
        if zhi1 > zhi2:
            zhi1, zhi2 = zhi2, zhi1
        key = (zhi1, zhi2)
        return cls._cache.get(key)

    @classmethod
    def calc_zhi_he(cls, mingpan):
        # yueling = mingpan.yueling.zhi.name
        sizhu = mingpan.sizhu
        ans = []
        for dz_cell1, dz_cell2 in combine([zhu[1] for zhu in sizhu], 2):
            match = cls.get_item(dz_cell1.zhi.name, dz_cell2.zhi.name)
            if match:  # and yueling in match.m_dz:
                ans.append(Connected(match, mingpan, [dz_cell1, dz_cell2]))
        return ans


    def __str__(self):
        return f'{self.zhi1.name}{self.zhi2.name}合{self.wuxing.name}'


class Zhi3He:
    """
    地支三合
    """
    _cache = {}
    _ban_he = {}  # 半合
    _gong_he = {}  # 拱合
    _inited = False

    def __new__(cls, zhi1, zhi2, zhi3, wuxing, m_dz):
        if cls._inited:
            raise RuntimeError(f'{cls.__name__} 已经加载，不能直接实例化，请使用 {cls.__name__}.get_item')

        key1 = tuple(sorted([zhi1, zhi2, zhi3]))
        key2 = tuple(sorted([zhi1, zhi2]))
        key3 = tuple(sorted([zhi2, zhi3]))
        key4 = tuple(sorted([zhi1, zhi3]))
        if key1 in cls._cache:
            return cls._cache[key1]
        else:
            self = super().__new__(cls)
            cls._cache[key1] = self
            cls._ban_he[key2] = self
            cls._ban_he[key3] = self
            cls._gong_he[key4] = self
            return self

    def __init__(self, zhi1, zhi2, zhi3, wuxing, m_dz):
        self.zhi1 = Zhi.get_item(zhi1)
        self.zhi2 = Zhi.get_item(zhi2)  # 中神
        self.zhi3 = Zhi.get_item(zhi3)
        self.wuxing = WuXing.get_item(wuxing)
        self.m_dz = m_dz

    @classmethod
    def init_zhis_3he(cls, items):
        """
        初始化的时候调用一次
        """
        for zhi1, zhi2, zhi3, wuxing, m_dz in items:
            cls(zhi1, zhi2, zhi3, wuxing, m_dz.replace(' ', ''))
        cls._inited = True

    @classmethod
    def get_item(cls, *zhis):
        key = tuple(sorted(zhis))
        if len(key) == 3:
            return cls._cache.get(key)
        else:
            assert len(key) == 2
            return cls._ban_he.get(key) or cls._gong_he.get(key)

    def same(self, *zhis):
        """
        是三合，半合，还是拱合
        """
        return len(zhis) == 3

    def partial_str(self, zhis):
        if self.zhi1.name not in zhis:
            return f'{self.zhi2.name}{self.zhi3.name}半合{self.wuxing.name}'
        elif self.zhi2.name not in zhis:
            return f'{self.zhi1.name}{self.zhi3.name}拱合{self.wuxing.name}'
        else:
            return f'{self.zhi1.name}{self.zhi2.name}半合{self.wuxing.name}'

    @classmethod
    def calc_zhi_3he(cls, mingpan):
        # yueling = mingpan.yueling.zhi.name
        sizhu = mingpan.sizhu
        zhis = [zhu[1] for zhu in sizhu]
        ans = []
        # 地支三合
        for dz_cell1, dz_cell2, dz_cell3 in combine(zhis, 3):
            match = cls.get_item(dz_cell1.zhi.name, dz_cell2.zhi.name, dz_cell3.zhi.name)
            if match:  # and yueling in match.m_dz:
                ans.append(Connected(match, mingpan, [dz_cell1, dz_cell2, dz_cell3]))
        # 已经三合了就不要检查半合了，否则重复
        if ans: return ans
        # 地支半合和拱合
        for dz_cell1, dz_cell2 in combine(zhis, 2):
            match = cls.get_item(dz_cell1.zhi.name, dz_cell2.zhi.name)
            if match:  # and yueling in match.m_dz:
                ans.append(Connected(match, mingpan, [dz_cell1, dz_cell2]))
        return ans

    def __str__(self):
        return f'{self.zhi1.name}{self.zhi2.name}{self.zhi3.name}三合{self.wuxing.name}'


class Zhi3Hui:
    """
    地支三会
    """
    _cache = {}
    _inited = False

    def __new__(cls, zhi1, zhi2, zhi3, wuxing, m_dz):
        if cls._inited:
            raise RuntimeError(f'{cls.__name__} 已经加载，不能直接实例化，请使用 {cls.__name__}.get_item')
        key = tuple(sorted([zhi1, zhi2, zhi3]))
        if key in cls._cache:
            return cls._cache[key]
        else:
            self = super().__new__(cls)
            cls._cache[key] = self
            return self

    def __init__(self, zhi1, zhi2, zhi3, wuxing, m_dz):
        self.zhi1 = Zhi.get_item(zhi1)
        self.zhi2 = Zhi.get_item(zhi2)
        self.zhi3 = Zhi.get_item(zhi3)
        self.wuxing = WuXing.get_item(wuxing)
        self.m_dz = m_dz

    @classmethod
    def init_zhis_3hui(cls, items):
        """
        初始化的时候调用一次
        """
        for zhi1, zhi2, zhi3, wuxing, m_dz in items:
            cls(zhi1, zhi2, zhi3, wuxing, m_dz.replace(' ', ''))
        cls._inited = True

    @classmethod
    def get_item(cls, zhi1, zhi2, zhi3):
        key = tuple(sorted([zhi1, zhi2, zhi3]))
        return cls._cache.get(key)

    @classmethod
    def calc_zhi_3hui(cls, mingpan):
        # yueling = mingpan.yueling.zhi.name
        sizhu = mingpan.sizhu
        zhis = [zhu[1] for zhu in sizhu]
        ans = []
        # 地支三会
        for dz_cell1, dz_cell2, dz_cell3 in combine(zhis, 3):
            match = cls.get_item(dz_cell1.zhi.name, dz_cell2.zhi.name, dz_cell3.zhi.name)
            if match:  # and yueling in match.m_dz:
                ans.append(Connected(match, mingpan, [dz_cell1, dz_cell2, dz_cell3]))
        return ans

    def __str__(self):
        return f'{self.zhi1.name}{self.zhi2.name}{self.zhi3.name}三会{self.wuxing.name}'
